import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { normalizeUserOwnProfile, UserOwnProfileApi, UserOwnProfileModel } from 'store/models';
import RootStore, { IGlobalStore } from 'store/RootStore';
import ApiStore, { FetchMeta } from 'store/RootStore/ApiStore';

type GithubLoginApiBody = {
    access_token: string;
    scope: string;
    token_type: string;
};

type PrivateFields =
    | '_login'
    | '_accessToken'
    | '_userOwnProfile'
    | '_accessTokenMeta'
    | '_userOwnProfileMeta'
    | '_fetchRequestsMeta';

export default class GithubOAuthStore implements IGlobalStore {
    private readonly _ApiStore = new ApiStore();
    private _login = Boolean(localStorage.getItem('access_token'));
    private _accessToken = localStorage.getItem('access_token');
    private _userOwnProfile?: UserOwnProfileModel;
    private _accessTokenMeta = FetchMeta.INITIAL;
    private _userOwnProfileMeta = FetchMeta.INITIAL;
    private readonly _accessTokenReaction: IReactionDisposer;
    private readonly _fetchMetaReaction: IReactionDisposer;

    constructor() {
        makeObservable<GithubOAuthStore, PrivateFields>(this, {
            _login: observable,
            _accessToken: observable,
            _userOwnProfile: observable,
            _accessTokenMeta: observable,
            _userOwnProfileMeta: observable,
            isLogin: computed,
            userOwnProfile: computed,
            _fetchRequestsMeta: computed,
            accessTokenMeta: computed,
            githubAuthorize: action.bound,
            fetchUserOwnProfile: action.bound,
            setAccessToken: action.bound,
        });

        this._accessTokenReaction = reaction(
            () => this._accessToken,
            (access) => {
                this._login = !!access;
            },
        );

        if (this._login) {
            this.fetchUserOwnProfile();
        }

        this._fetchMetaReaction = reaction(
            () => this._fetchRequestsMeta,
            (meta) => {
                RootStore.pageLoader.getFetchRequestsMeta(meta);
            },
        );
    }

    private get _fetchRequestsMeta(): FetchMeta[] {
        return [this._accessTokenMeta, this._userOwnProfileMeta];
    }

    get isLogin(): boolean {
        return this._login;
    }

    get accessTokenMeta(): FetchMeta {
        return this._accessTokenMeta;
    }
    get userOwnProfile(): UserOwnProfileModel | undefined {
        return this._userOwnProfile;
    }

    setAccessToken(accessToken: string | null): void {
        this._accessToken = accessToken;
    }

    async githubAuthorize(code: string): Promise<void> {
        this._accessTokenMeta = FetchMeta.FETCHING;
        try {
            const result = await fetch(`${process.env.ACCESS_TOKEN_API}?code=${code}`, {
                headers: {
                    accept: 'application/json',
                },
            }).then<GithubLoginApiBody>((res) => res.json());
            runInAction(() => {
                localStorage.setItem('access_token', result.access_token);
                this.setAccessToken(result.access_token);
                this._accessTokenMeta = FetchMeta.FETCHED;
                this.fetchUserOwnProfile();
            });
        } catch {
            runInAction(() => {
                this._accessTokenMeta = FetchMeta.ERROR;
                localStorage.removeItem('access_token');
                this.setAccessToken(null);
            });
        }
    }

    fetchUserOwnProfile = async (): Promise<void> => {
        this._userOwnProfileMeta = FetchMeta.FETCHING;
        try {
            const result = await this._ApiStore.getApiRequest<UserOwnProfileApi>(`/user`);
            runInAction(() => {
                this._userOwnProfile = normalizeUserOwnProfile(result.data);
                this._userOwnProfileMeta = FetchMeta.FETCHED;
            });
        } catch {
            runInAction(() => {
                this._userOwnProfileMeta = FetchMeta.ERROR;
                this._userOwnProfile = undefined;
            });
        }
    };

    destroy(): void {
        this._fetchMetaReaction();
        this._accessTokenReaction();
    }
}
